//
//  receiver.cpp
//  Wi-Fi Party remade-1
//
//  Created by Vieta Ken on 2021/6/4.
//

#include "receiver.hpp"

#include <cstdio>
#include <list>
#include <netinet/in.h>
#include <pthread.h>
#include "app.h"
#include "channel.hpp"
#include "packet.hpp"

auto receivePacketPool = Slab<Packet>(10);
auto processArgsPool = Slab<_arguments_for_process_a_packet>(10);

static void *process_a_packet(void *arg_raw){
    struct _arguments_for_process_a_packet *arg = (struct _arguments_for_process_a_packet *)arg_raw;
    for (std::list<Channel *>::iterator channel = arg->receiver->channels->begin(); channel != arg->receiver->channels->end(); channel++)
    {
        if ((*channel)->channel_id == arg->received_packet->header->channel)
        {
            // This is the channel
            (*channel)->receive_packet(arg->received_packet);
            break;
        }
    }
    processArgsPool.release(arg); // Freed #2
    return NULL;
}

static void *start_receive_async(void *arg){
    Receiver *recv = (Receiver *)arg;
    recv->receive_blocking();
    return NULL;
}

Receiver::Receiver(int socket){
    this->known_hosts=new std::map<int,std::map<unsigned int, bool>*>;
    this->channels=new std::list<Channel*>;
    this->socket=socket;
}

void Receiver::addChannel(Channel *channel){
    this->channels->push_back(channel);
    channel->receiver=this;
}
void Receiver::receive_forever(){
    pthread_t thread_id;
    if(pthread_create(&thread_id, NULL, start_receive_async, this)){
        throw "Failed to create POSIX thread";
    }
}

void Receiver::receive_blocking(){
    struct sockaddr_in receive_address={
        .sin_family=AF_INET,
        .sin_port=htons(PORT),
        .sin_addr={
            .s_addr=htonl(INADDR_ANY)
        },
        .sin_zero={0},
        #ifdef __APPLE__
        .sin_len=sizeof(struct sockaddr_in)
        #endif
    };
    int flag=1;

    if(setsockopt(this->socket, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag))!=0){
        printf("Failed to enable broadcast\n");//Allow broadcast
    }
    if(setsockopt(this->socket, IPPROTO_IP, IP_MULTICAST_LOOP, &flag, sizeof(flag))!=0){
        printf("Failed to enable loopback\n");//Sent packets should loop back to this device
    }

    //Join the multicast group
    struct ip_mreq multicast_request={
        .imr_multiaddr={
            inet_addr(MULTICAST_ADDRESS_STR)
        },
        .imr_interface={
            htonl(INADDR_ANY)
        }
    };
    if(setsockopt(this->socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &multicast_request, sizeof(multicast_request))!=0){
        printf("Failed to join multicast group\n");
        perror("Joining multicast group");
    }

    int call_result=bind(this->socket, (struct sockaddr *)&receive_address, sizeof(receive_address));
    if(call_result > 0){
        throw "Failed to set up UDP listener";
    }
    struct _arguments_for_process_a_packet *args;
    while(true){
        Packet *received_packet=new (receivePacketPool.alloc()) Packet(this->socket, IDENTIFIER);//Should be freed #1 by Channel TODO:try/catch

        //Packet received, now process it.
        args=processArgsPool.alloc();//Should be freed #2 by function process_a_packet
        args->received_packet=received_packet;
        args->receiver=this;
        process_a_packet(args);
    }
}




Receiver::~Receiver(){
    struct ip_mreq multicast_request = {
        .imr_multiaddr = {
            inet_addr(MULTICAST_ADDRESS_STR)
        },
        .imr_interface = {
            htonl(INADDR_ANY)
        }
    };
    if (setsockopt(this->socket, IPPROTO_IP, IP_DROP_MEMBERSHIP, &multicast_request, sizeof(multicast_request)) != 0)
    {
        printf("Failed to leave multicast group\n");
        perror("Leaving multicast group");
    }
}
